Getting started
First, we need to load our packages like we did in our previous
notebook.
library(tidyverse)
── Attaching core tidyverse packages ────────────────────────────────────────────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.1.4 ✔ readr 2.1.5
✔ forcats 1.0.0 ✔ stringr 1.5.1
✔ ggplot2 3.5.1 ✔ tibble 3.3.0
✔ lubridate 1.9.3 ✔ tidyr 1.3.1
✔ purrr 1.0.4 ── Conflicts ──────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(snotelr)
library(cowplot)
Attaching package: ‘cowplot’
The following object is masked from ‘package:lubridate’:
stamp
theme_set(theme_cowplot())
library(knitr)
library(Kendall)
library(trend)
Next, we will source the functions I’ve created to
derive water year information and compute snow metrics. These files can
be found in analysis/functions.
source("functions/snow_metrics.R")
source("functions/meteo_metrics.R")
source("functions/water_year.R")
You can see these functions now in your Environment pane. All the
source function does is run the R scripts that contain the
functions I’ve built. (We’ll return to the actual functions later.)

Defining our subset
Useful metadata
In our work we will want to use all or most of the long-term SNOTEL
records, but here we’ll start with a subset. To start building this
subset, we’ll need a couple data sources. One is the SNOTEL metadata we
can grab from snotelr:
snotel_info <- snotel_info()
snotel_info %>%
head()
The other is the HARBOR dataset from Scott Peckham:
harbor_url <- "https://raw.githubusercontent.com/peckhams/nextgen_basin_repo/refs/heads/main/__Collated/collated_basins_all.tsv"
basin_info <- read_tsv(harbor_url)
Rows: 30717 Columns: 51── Column specification ──────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: "\t"
chr (42): Site_ID, NWS_Loc_ID, GOES_ID, RFC, WFO/CWA, HSA, HUC, Site_Name, Site_Type, Stage_Data, PEDTS_Obs, State_Code, C...
dbl (9): Lon, Lat, Area, Minlon, Maxlon, Minlat, Maxlat, Closest_Site_Dist, HLR_Code_Outlet
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
basin_info %>%
head()
We discussed the SNOTEL file in our previous notebook. The HARBOR
(Harmonized Attributes of River Basins in One Repo) dataset is an
exhaustive accounting of sometimes overlapping basin data sources from
USGS NWIS, CAMELS, NWS River Forecast Centers, etc.
At first glance these two datasets have nothing in common, but buried
in the description column of snotel_info is a
HUC (hydrologic unit code) ID that we can match to the HUC
column in basin_info. We have to do a bit of string
manipulation first.
snotel_info <- snotel_info %>%
mutate(HUC = stringr::str_extract(string = description,
pattern = "(?<=\\().*(?=\\))"))
Now we that we’ve extracted the HUC ID from in between the
parentheses, we can join the two datasets.
all_info <- left_join(snotel_info,
basin_info,
by = "HUC")
Warning: Detected an unexpected many-to-many relationship between `x` and `y`.
The warning above indicates there are some rows in
snotel_info that have multiple matches in
basin_info and vice versa. This occurs when there are
multiple SNOTEL stations in a given HUC or when a SNOTEL station finds
itself in multiple nested basins.
SNOTEL data
Accessing station data
Now we’ll identify the site_id for each station in our
subset, put it in a vector, and download the data with
snotelr.
sites <- subset_info %>% pull(site_id)
NOTE: If you don’t want to wait while snotelr downloads the
dataset, skip ahead to the commented-out cell that says
df <- readRDS("../data/snotel_camels_subset.RDS"),
uncomment it, and run it.
df <- snotel_download(sites, internal = T)
Downloading site: hewinta , with id: 521
Downloading site: hole-in-rock , with id: 528
Downloading site: hams fork , with id: 509
Downloading site: echo peak , with id: 463
Downloading site: rubicon #2 , with id: 724
Downloading site: lamoille #3 , with id: 570
Downloading site: munson ridge , with id: 950
Downloading site: ward creek #3 , with id: 848
Downloading site: summit ranch , with id: 802
Downloading site: mt hood test site , with id: 651
Downloading site: lasal mountain , with id: 572
Downloading site: corral pass , with id: 418
Downloading site: olallie meadows , with id: 672
Downloading site: hagans meadow , with id: 508
Downloading site: heavenly valley , with id: 518
Downloading site: independence lake , with id: 541
Downloading site: joe wright , with id: 551
Downloading site: vail mountain , with id: 842
Downloading site: franklin basin , with id: 484
Downloading site: mud ridge , with id: 655
Downloading site: north fork , with id: 666
Downloading site: daniels-strawberry , with id: 435
Downloading site: pickle keg , with id: 691
Downloading site: steel creek park , with id: 790
Downloading site: vernon creek , with id: 844
Downloading site: dome lake , with id: 451
Downloading site: little warm , with id: 585
Downloading site: shell creek , with id: 751
Downloading site: spring creek divide , with id: 779
Downloading site: many glacier , with id: 613
Downloading site: bear creek , with id: 321
Downloading site: west yellowstone , with id: 924
Downloading site: northeast entrance , with id: 670
First, we’ll select just the columns we need.
# Downselect to just the columns we want
# Rename var columns to include units
df <- df %>%
select(site_id, date,
swe_mm = snow_water_equivalent,
snow_depth_mm = snow_depth,
ppt_mm = precipitation,
tair_av_degC = temperature_mean)
Then we’ll add date information:
# Add additional date information
df <- df %>%
mutate(date = ymd(date),
wyear = wateryear(date),
dowy = day_of_wateryear(date))
Then we’ll save it as an RDS file (I’ve commented this part out
because it doesn’t need to re-run).
Note: I saved the file for two reasons: 1) in case we have
bandwidth issues and 2) to analyze again in the next notebook.
# saveRDS(object = df,
# file = "../data/snotel_camels_subset.RDS")
We can uncomment out the following if we need to import the saved
data.
# df <- readRDS("../data/snotel_camels_subset.RDS")
Pre-processing the data
We want to only include years in our analysis with a certain
percentage of valid SWE observations. We’re going to make that threshold
100% here (SNOTEL SWE has a relatively robust QC protocol), but you can
choose other values.
# Calculate the percentage of valid SWE observations per water year
site_summary_by_wyear <- df %>%
group_by(site_id, wyear) %>%
summarize(n_expected = ifelse(any(wyear %% 4 == 0),
366,
365),
n_obs = sum(!is.na(swe_mm)),
pct_valid = (n_obs / n_expected) * 100) %>%
ungroup()
`summarise()` has grouped output by 'site_id'. You can override using the `.groups` argument.
# Provide a threshold of valid obs that we'll consider to be a complete water year
pct_valid_thresh = 100
# Identify sites and water years that meet our threshold
valid_sites_wyears <- site_summary_by_wyear %>%
filter(pct_valid >= pct_valid_thresh) %>%
select(site_id, wyear)
# Filter using inner join to only sites and water years in our valid data frame
df_filter <-
inner_join(df, valid_sites_wyears,
by = c("site_id", "wyear"))
Calculate metrics
Now we’ll use our SNOTEL data subset to compute various metrics:
- Maximum snow water equivalent (SWE)
- Maximum SWE day of water year (DOWY)
- Snow cover duration
- Snow-on day
- Snow-off day
- Melt season length
- Snowmelt rate
- Snow seasonality metric (SSM)
- April 1 SWE
- Snowmelt before max SWE
- Snowmelt before max SWE percent of total snowmelt
- Snowmelt before max SWE to max SWE ratio
- Snowmelt center of mass DOWY
- Peak SWE to annual precipitation ratio
- And several meteorological data metrics
metrics <- df_filter %>%
group_by(site_id, wyear) %>%
summarize(max_swe_mm = maxSWE(swe_mm),
max_swe_dowy = maxSWE_DOWY(swe_mm, dowy),
scd_days = scd(swe_mm),
snow_on_day = firstSnow(swe_mm, dowy),
snow_off_day = lastSnow(swe_mm, dowy, max_swe_dowy),
melt_season_days = meltSeason(max_swe_dowy, snow_off_day),
melt_rate_mm_d = meltRate(melt_season_days, max_swe_mm),
ssm = snowSeasonality(swe_mm),
swe_apr1_mm = april1SWE(swe_mm, dowy, wyear),
pre_max_swe_melt_mm = preMaxMelt(swe_mm, dowy, max_swe_dowy),
pre_max_swe_melt_total_melt_pct = preMaxMeltPctTotalMelt(pre_max_swe_melt_mm, swe_mm),
pre_max_swe_melt_max_swe_ratio = preMaxMeltMaxSWERatio(pre_max_swe_melt_mm, max_swe_mm),
melt_com_day = meltCoM(swe_mm, dowy),
swe_to_ppt_ratio = sweToPptRatio(max_swe_mm, ppt_mm),
fall_ppt_mm = seasonalPrecip(ppt_mm, date, SEASON = "fall"),
winter_ppt_mm = seasonalPrecip(ppt_mm, date, SEASON = "winter"),
spring_ppt_mm = seasonalPrecip(ppt_mm, date, SEASON = "spring"),
summer_ppt_mm = seasonalPrecip(ppt_mm, date, SEASON = "summer"),
annual_ppt_mm = totalPrecip(ppt_mm),
fall_tair_degC = seasonalTemp(tair_av_degC, date, SEASON = "fall"),
winter_tair_degC = seasonalTemp(tair_av_degC, date, SEASON = "winter"),
spring_tair_degC = seasonalTemp(tair_av_degC, date, SEASON = "spring"),
summer_tair_degC = seasonalTemp(tair_av_degC, date, SEASON = "summer"),
annual_tair_degC = meanTemp(tair_av_degC))
`summarise()` has grouped output by 'site_id'. You can override using the `.groups` argument.
We can take a quick look at these tabular data.
metrics %>%
head()
We can also plot some of the outcomes.
Maximum SWE
ggplot(metrics, aes(wyear, max_swe_mm)) +
geom_line() +
facet_wrap(~as.factor(site_id), ncol = 4) +
labs(x = "Water Year", y = "Max SWE (mm)")

Snow cover duration (SCD)
ggplot(metrics, aes(wyear, scd_days)) +
geom_line() +
facet_wrap(~as.factor(site_id), ncol = 4) +
labs(x = "Water Year", y = "SCD (d)")

Snow seasonality metric (SSM)
ggplot(metrics, aes(wyear, ssm)) +
geom_line() +
facet_wrap(~as.factor(site_id), ncol = 4) +
labs(x = "Water Year", y = "SSM")

Pre-max SWE snowmelt as percent of total snowmelt
ggplot(metrics, aes(wyear, pre_max_swe_melt_total_melt_pct)) +
geom_line() +
facet_wrap(~as.factor(site_id), ncol = 4) +
labs(x = "Water Year", y = "Pre-Max SWE Melt (%)")

However, it is hard to tell what, if anything, is happening at our
sites over time. So what we’ll do next is compute some trends.
Compute trends
First we’ll make a function to make a “long” version of our
metrics dataframe and then compute various trend stats,
such as the Mann-Kendall p-value and Sen’s slope.
analyze_snow_trends <- function(DF) {
# Pivot to make long dataframe
df_long <- DF %>%
pivot_longer(
cols = -c(site_id, wyear), # could add column names as argument to make function generalizable
names_to = "metric",
values_to = "value"
) %>%
filter(!is.na(value)) %>% # remove NAs
filter(!is.infinite(value)) # remove Infs
# Take df_long and compute trend values per site_id and metric
df_long %>%
group_by(site_id, metric) %>%
summarise(
n_years = n(),
mann_kendall = list(MannKendall(value)),
sens_slope = list(sens.slope(value)),
.groups = "drop"
) %>%
mutate(
mk_tau = map_dbl(mann_kendall, ~ .x$tau),
mk_p = map_dbl(mann_kendall, ~ .x$sl),
sen_slope = map_dbl(sens_slope, ~ .x$estimates),
sen_p = map_dbl(sens_slope, ~ .x$p.value)
) %>%
select(site_id, metric, n_years, mk_tau, mk_p, sen_slope, sen_p)
}
Now we’ll apply this function to metrics.
trends <- analyze_snow_trends(metrics)
trends %>%
head()
We can view distributions of the Mann-Kendall p-values and Sen’s
slopes.
ggplot(trends, aes(mk_p)) +
geom_density(fill = "lightblue") +
geom_vline(xintercept = 0.05, lty = "dashed") +
facet_wrap(~metric, ncol = 3, scales = "free") +
labs(x = "Mann-Kendall p-values", y = "Density")

ggplot(trends, aes(sen_slope)) +
geom_density(fill = "lightblue") +
geom_vline(xintercept = 0, lty = "dashed") +
facet_wrap(~metric, ncol = 3, scales = "free") +
labs(x = "Sen's Slopes", y = "Density")

We can now re-examine some of the previous plots, looking at only
sites with significant changes.
Maximum SWE with statistically siginificant trends
p_thresh = 0.05
metrics %>%
filter(site_id %in% filter(trends, metric == "max_swe_mm" & mk_p < 0.05)$site_id) %>%
ggplot(aes(wyear, max_swe_mm)) +
geom_line() +
geom_smooth(method = "lm", se = F, color = "red") +
facet_wrap(~as.factor(site_id), scales = "free") +
labs(x = "Water Year", y = "Max SWE (mm)")

SCD with statistically siginificant trends
metrics %>%
filter(site_id %in% filter(trends, metric == "scd_days" & mk_p < 0.05)$site_id) %>%
ggplot(aes(wyear, scd_days)) +
geom_line() +
geom_smooth(method = "lm", se = F, color = "red") +
facet_wrap(~as.factor(site_id), scales = "free") +
labs(x = "Water Year", y = "SCD (d)")

Snow seasonality metric (SSM) with statistically siginificant
trends
metrics %>%
filter(site_id %in% filter(trends, metric == "ssm" & mk_p < 0.05)$site_id) %>%
ggplot(aes(wyear, ssm)) +
geom_line() +
geom_smooth(method = "lm", se = F, color = "red") +
facet_wrap(~as.factor(site_id), scales = "free") +
labs(x = "Water Year", y = "SSM")

Pre-max SWE snowmelt as percent of total snowmelt with statistically
siginificant trends
metrics %>%
filter(site_id %in% filter(trends, metric == "pre_max_swe_melt_total_melt_pct" & mk_p < 0.05)$site_id) %>%
ggplot(aes(wyear, pre_max_swe_melt_total_melt_pct)) +
geom_line() +
geom_smooth(method = "lm", se = F, color = "red") +
facet_wrap(~as.factor(site_id), scales = "free", ncol =3) +
labs(x = "Water Year", y = "Pre-Max SWE Melt (%)")

We can summarize the trends even further to see which ones have the
most prevalent statistically significant results.
p_thresh = 0.05
trend_summary <- trends %>%
group_by(metric) %>%
summarize(mk_pct_sig = (sum(mk_p < p_thresh) / n()) * 100,
sen_slope_av = mean(sen_slope),
sen_slope_av_sig = mean(sen_slope[mk_p < p_thresh]))
trend_summary %>%
arrange(-mk_pct_sig)
Now that we’ve looked at these data, we’ll explore the use of spatial
SWE information in our work.
LS0tCnRpdGxlOiAnU3RlcCAwMjogQ29tcHV0aW5nIFJlbGV2YW50IFNub3cgTWV0cmljcyBvbiBhIFN1YnNldCBvZiBTTk9URUwgU3RhdGlvbnMnCmF1dGhvcjogIktlaXRoIEplbm5pbmdzIgpvdXRwdXQ6CiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAotLS0KCiMgR2V0dGluZyBzdGFydGVkCgpGaXJzdCwgd2UgbmVlZCB0byBsb2FkIG91ciBwYWNrYWdlcyBsaWtlIHdlIGRpZCBpbiBvdXIgcHJldmlvdXMgbm90ZWJvb2suCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc25vdGVscikKbGlicmFyeShjb3dwbG90KQp0aGVtZV9zZXQodGhlbWVfY293cGxvdCgpKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KEtlbmRhbGwpCmxpYnJhcnkodHJlbmQpCmBgYAoKTmV4dCwgd2Ugd2lsbCBgc291cmNlYCB0aGUgZnVuY3Rpb25zIEkndmUgY3JlYXRlZCB0byBkZXJpdmUgd2F0ZXIgeWVhciBpbmZvcm1hdGlvbiBhbmQgY29tcHV0ZSBzbm93IG1ldHJpY3MuIFRoZXNlIGZpbGVzIGNhbiBiZSBmb3VuZCBpbiBgYW5hbHlzaXMvZnVuY3Rpb25zYC4KCmBgYHtyfQpzb3VyY2UoImZ1bmN0aW9ucy9zbm93X21ldHJpY3MuUiIpCnNvdXJjZSgiZnVuY3Rpb25zL21ldGVvX21ldHJpY3MuUiIpCnNvdXJjZSgiZnVuY3Rpb25zL3dhdGVyX3llYXIuUiIpCmBgYAoKWW91IGNhbiBzZWUgdGhlc2UgZnVuY3Rpb25zIG5vdyBpbiB5b3VyIEVudmlyb25tZW50IHBhbmUuIEFsbCB0aGUgYHNvdXJjZWAgZnVuY3Rpb24gZG9lcyBpcyBydW4gdGhlIFIgc2NyaXB0cyB0aGF0IGNvbnRhaW4gdGhlIGZ1bmN0aW9ucyBJJ3ZlIGJ1aWx0LiAoV2UnbGwgcmV0dXJuIHRvIHRoZSBhY3R1YWwgZnVuY3Rpb25zIGxhdGVyLikKCjxpbWcgc3JjPSIuLi9pbWFnZXMvcnN0dWRpb19wYWNrYWdlX3BhbmUucG5nIiAgd2lkdGg9IjUwMCIvPgoKIyBEZWZpbmluZyBvdXIgc3Vic2V0CgojIyBVc2VmdWwgbWV0YWRhdGEKCkluIG91ciB3b3JrIHdlIHdpbGwgd2FudCB0byB1c2UgYWxsIG9yIG1vc3Qgb2YgdGhlIGxvbmctdGVybSBTTk9URUwgcmVjb3JkcywgYnV0IGhlcmUgd2UnbGwgc3RhcnQgd2l0aCBhIHN1YnNldC4gVG8gc3RhcnQgYnVpbGRpbmcgdGhpcyBzdWJzZXQsIHdlJ2xsIG5lZWQgYSBjb3VwbGUgZGF0YSBzb3VyY2VzLiBPbmUgaXMgdGhlIFNOT1RFTCBtZXRhZGF0YSB3ZSBjYW4gZ3JhYiBmcm9tIGBzbm90ZWxyYDoKCmBgYHtyfQpzbm90ZWxfaW5mbyA8LSBzbm90ZWxfaW5mbygpCnNub3RlbF9pbmZvICU+JSAKICBoZWFkKCkKYGBgCgpUaGUgb3RoZXIgaXMgdGhlIEhBUkJPUiBkYXRhc2V0IGZyb20gU2NvdHQgUGVja2hhbToKCmBgYHtyfQpoYXJib3JfdXJsIDwtICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcGVja2hhbXMvbmV4dGdlbl9iYXNpbl9yZXBvL3JlZnMvaGVhZHMvbWFpbi9fX0NvbGxhdGVkL2NvbGxhdGVkX2Jhc2luc19hbGwudHN2IgpiYXNpbl9pbmZvIDwtIHJlYWRfdHN2KGhhcmJvcl91cmwpCmJhc2luX2luZm8gJT4lIAogIGhlYWQoKQpgYGAKCldlIGRpc2N1c3NlZCB0aGUgU05PVEVMIGZpbGUgaW4gb3VyIHByZXZpb3VzIG5vdGVib29rLiBUaGUgSEFSQk9SIChIYXJtb25pemVkIEF0dHJpYnV0ZXMgb2YgUml2ZXIgQmFzaW5zIGluIE9uZSBSZXBvKSBkYXRhc2V0IGlzIGFuIGV4aGF1c3RpdmUgYWNjb3VudGluZyBvZiBzb21ldGltZXMgb3ZlcmxhcHBpbmcgYmFzaW4gZGF0YSBzb3VyY2VzIGZyb20gVVNHUyBOV0lTLCBDQU1FTFMsIE5XUyBSaXZlciBGb3JlY2FzdCBDZW50ZXJzLCBldGMuCgpBdCBmaXJzdCBnbGFuY2UgdGhlc2UgdHdvIGRhdGFzZXRzIGhhdmUgbm90aGluZyBpbiBjb21tb24sIGJ1dCBidXJpZWQgaW4gdGhlIGBkZXNjcmlwdGlvbmAgY29sdW1uIG9mIGBzbm90ZWxfaW5mb2AgaXMgYSBIVUMgKGh5ZHJvbG9naWMgdW5pdCBjb2RlKSBJRCB0aGF0IHdlIGNhbiBtYXRjaCB0byB0aGUgYEhVQ2AgY29sdW1uIGluIGBiYXNpbl9pbmZvYC4gV2UgaGF2ZSB0byBkbyBhIGJpdCBvZiBzdHJpbmcgbWFuaXB1bGF0aW9uIGZpcnN0LgoKYGBge3J9CnNub3RlbF9pbmZvIDwtIHNub3RlbF9pbmZvICU+JSAKICBtdXRhdGUoSFVDID0gc3RyaW5ncjo6c3RyX2V4dHJhY3Qoc3RyaW5nID0gZGVzY3JpcHRpb24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXR0ZXJuID0gIig/PD1cXCgpLiooPz1cXCkpIikpCmBgYAoKTm93IHdlIHRoYXQgd2UndmUgZXh0cmFjdGVkIHRoZSBIVUMgSUQgZnJvbSBpbiBiZXR3ZWVuIHRoZSBwYXJlbnRoZXNlcywgd2UgY2FuIGpvaW4gdGhlIHR3byBkYXRhc2V0cy4KCmBgYHtyfQphbGxfaW5mbyA8LSBsZWZ0X2pvaW4oc25vdGVsX2luZm8sCiAgICAgICAgICAgICAgICAgYmFzaW5faW5mbywKICAgICAgICAgICAgICAgICBieSA9ICJIVUMiKQpgYGAKClRoZSB3YXJuaW5nIGFib3ZlIGluZGljYXRlcyB0aGVyZSBhcmUgc29tZSByb3dzIGluIGBzbm90ZWxfaW5mb2AgdGhhdCBoYXZlIG11bHRpcGxlIG1hdGNoZXMgaW4gYGJhc2luX2luZm9gIGFuZCB2aWNlIHZlcnNhLiBUaGlzIG9jY3VycyB3aGVuIHRoZXJlIGFyZSBtdWx0aXBsZSBTTk9URUwgc3RhdGlvbnMgaW4gYSBnaXZlbiBIVUMgb3Igd2hlbiBhIFNOT1RFTCBzdGF0aW9uIGZpbmRzIGl0c2VsZiBpbiBtdWx0aXBsZSBuZXN0ZWQgYmFzaW5zLiAKCiMjIFVzaW5nIG1ldGFkYXRhIHRvIHNlbGVjdCBTTk9URUwgc3RhdGlvbnMKClRoZSBjb21iaW5lZCBkYXRhZnJhbWUgaW5jbHVkZXMgbXVsdGlwbGUgY29sdW1ucyB3ZSBjYW4gdXNlIHRvIHNwbGl0IHRoZSBkYXRhLiBBIGZldyBleGFtcGxlczoKCi0gQWxsIFNOT1RFTCBzdGF0aW9ucyBpbiBhIFVTR1MgR0FHRVMgSUkgUmVmZXJlbmNlIGJhc2luCiAgLSBgciBhbGxfaW5mbyAlPiUgZmlsdGVyKElzX0dBR0VTMl9SZWYgPT0gIlkiKSAlPiUgbnJvdygpYCBtYXRjaGVzCi0gU05PVEVMIHN0YXRpb25zIGFib3ZlIDE1MDAgbSBpbiBPcmVnb24gCiAgLSBgciBhbGxfaW5mbyAlPiUgZmlsdGVyKHN0YXRlID09ICJPUiIgJiBlbGV2ID4gMTUwMCkgJT4lIG5yb3coKWAgbWF0Y2hlcwotIFNOT1RFTCBzdGF0aW9ucyBpbiB0aGUgV2VzdE10bnMgZWNvcmVnaW9uIHdpdGhpbiBhIENBTUVMUyBiYXNpbiB3aXRoIGEgc25vdy1kb20gaHlkcm9ncmFwaCB0eXBlCiAgLSBgciBhbGxfaW5mbyAlPiUgZmlsdGVyKEVjb19SZWdpb24gPT0gIldlc3RNbnRzIiAmIElzX0NBTUVMUyA9PSAiWSIgJiBIZ3JhcGhfVHlwZSA9PSAic25vdy1kb20iKSAlPiUgbnJvdygpYCBtYXRjaAotIEFuZCBzbyBvbi4uLgoKRm9yIG5vdywgd2UnbGwgc3RhcnQgd2l0aCBhIHJlbGF0aXZlbHkgc21hbGwgU05PVEVMIGFuZCBVU0dTIEdhZ2VzIElJIFJlZmVyZW5jZSBzdWJzZXQgd2l0aCBhdCBsZWFzdCA0MCB5cnMgb2YgZGF0YS4KCmBgYHtyfQpzdWJzZXRfaW5mbyA8LSBhbGxfaW5mbyAlPiUgCiAgZmlsdGVyKHllYXIoc3RhcnQpIDw9IDE5ODUgJiB5ZWFyKGVuZCkgPj0gMjAyNCkgJT4lIAogIGZpbHRlcihJc19HQUdFUzJfUmVmID09ICJZIikKYGBgCgpUaGVyZSBhcmUgc29tZSBkdXBsaWNhdGVzIGluIHRoZSBzdWJzZXQsIHNvIHdlJ2xsIGZpbHRlciB0byBqdXN0IHRoZSBoaWdoZXN0IGVsZXZhdGlvbiBiYXNpbnMgKGFzc3VtaW5nIHRoZXkncmUgbW9yZSByZXByZXNlbnRhdGl2ZSBvZiB0aGUgU05PVEVMLW9ic2VydmVkIHNub3cgY29uZGl0aW9ucykuCgpgYGB7cn0Kc3Vic2V0X2luZm8gPC0gc3Vic2V0X2luZm8gJT4lIAogIGdyb3VwX2J5KHNpdGVfaWQpICU+JSAKICBzbGljZV9tYXgob3JkZXJfYnkgPSBFbGV2LCB3aXRoX3RpZXMgPSBGQUxTRSkgJT4lIAogIHVuZ3JvdXAoKQpgYGAKCk5vdyB3ZSBoYXZlIG91ciBzdWJzZXQgb2YgYHIgc3Vic2V0X2luZm8gJT4lIG5yb3coKWAgU05PVEVMIHN0YXRpb25zLgoKIyBTTk9URUwgZGF0YQoKIyMgQWNjZXNzaW5nIHN0YXRpb24gZGF0YQoKTm93IHdlJ2xsIGlkZW50aWZ5IHRoZSBgc2l0ZV9pZGAgZm9yIGVhY2ggc3RhdGlvbiBpbiBvdXIgc3Vic2V0LCBwdXQgaXQgaW4gYSB2ZWN0b3IsIGFuZCBkb3dubG9hZCB0aGUgZGF0YSB3aXRoIGBzbm90ZWxyYC4KCmBgYHtyfQpzaXRlcyA8LSBzdWJzZXRfaW5mbyAlPiUgcHVsbChzaXRlX2lkKQpgYGAKCioqTk9URTogSWYgeW91IGRvbid0IHdhbnQgdG8gd2FpdCB3aGlsZSBzbm90ZWxyIGRvd25sb2FkcyB0aGUgZGF0YXNldCwgc2tpcCBhaGVhZCB0byB0aGUgY29tbWVudGVkLW91dCBjZWxsIHRoYXQgc2F5cyBgZGYgPC0gcmVhZFJEUygiLi4vZGF0YS9zbm90ZWxfY2FtZWxzX3N1YnNldC5SRFMiKWAsIHVuY29tbWVudCBpdCwgYW5kIHJ1biBpdC4qKgoKYGBge3J9CmRmIDwtIHNub3RlbF9kb3dubG9hZChzaXRlcywgaW50ZXJuYWwgPSBUKQpgYGAKCkZpcnN0LCB3ZSdsbCBzZWxlY3QganVzdCB0aGUgY29sdW1ucyB3ZSBuZWVkLgoKYGBge3J9CiMgRG93bnNlbGVjdCB0byBqdXN0IHRoZSBjb2x1bW5zIHdlIHdhbnQKIyBSZW5hbWUgdmFyIGNvbHVtbnMgdG8gaW5jbHVkZSB1bml0cwpkZiA8LSBkZiAlPiUgCiAgc2VsZWN0KHNpdGVfaWQsIGRhdGUsCiAgICAgICAgIHN3ZV9tbSA9IHNub3dfd2F0ZXJfZXF1aXZhbGVudCwKICAgICAgICAgc25vd19kZXB0aF9tbSA9IHNub3dfZGVwdGgsCiAgICAgICAgIHBwdF9tbSA9IHByZWNpcGl0YXRpb24sCiAgICAgICAgIHRhaXJfYXZfZGVnQyA9IHRlbXBlcmF0dXJlX21lYW4pCmBgYAoKVGhlbiB3ZSdsbCBhZGQgZGF0ZSBpbmZvcm1hdGlvbjoKCmBgYHtyfQojIEFkZCBhZGRpdGlvbmFsIGRhdGUgaW5mb3JtYXRpb24KZGYgPC0gZGYgJT4lIAogIG11dGF0ZShkYXRlID0geW1kKGRhdGUpLAogICAgICAgICB3eWVhciA9IHdhdGVyeWVhcihkYXRlKSwKICAgICAgICAgZG93eSA9IGRheV9vZl93YXRlcnllYXIoZGF0ZSkpCmBgYAoKVGhlbiB3ZSdsbCBzYXZlIGl0IGFzIGFuIFJEUyBmaWxlIChJJ3ZlIGNvbW1lbnRlZCB0aGlzIHBhcnQgb3V0IGJlY2F1c2UgaXQgZG9lc24ndCBuZWVkIHRvIHJlLXJ1bikuCgoqTm90ZTogSSBzYXZlZCB0aGUgZmlsZSBmb3IgdHdvIHJlYXNvbnM6IDEpIGluIGNhc2Ugd2UgaGF2ZSBiYW5kd2lkdGggaXNzdWVzIGFuZCAyKSB0byBhbmFseXplIGFnYWluIGluIHRoZSBuZXh0IG5vdGVib29rLioKCmBgYHtyfQojIHNhdmVSRFMob2JqZWN0ID0gZGYsCiMgICAgICAgICBmaWxlID0gIi4uL2RhdGEvc25vdGVsX2NhbWVsc19zdWJzZXQuUkRTIikKYGBgCgpXZSBjYW4gdW5jb21tZW50IG91dCB0aGUgZm9sbG93aW5nIGlmIHdlIG5lZWQgdG8gaW1wb3J0IHRoZSBzYXZlZCBkYXRhLgoKYGBge3J9CiMgZGYgPC0gcmVhZFJEUygiLi4vZGF0YS9zbm90ZWxfY2FtZWxzX3N1YnNldC5SRFMiKQpgYGAKCiMjIFByZS1wcm9jZXNzaW5nIHRoZSBkYXRhCgpXZSB3YW50IHRvIG9ubHkgaW5jbHVkZSB5ZWFycyBpbiBvdXIgYW5hbHlzaXMgd2l0aCBhIGNlcnRhaW4gcGVyY2VudGFnZSBvZiB2YWxpZCBTV0Ugb2JzZXJ2YXRpb25zLiBXZSdyZSBnb2luZyB0byBtYWtlIHRoYXQgdGhyZXNob2xkIDEwMCUgaGVyZSAoU05PVEVMIFNXRSBoYXMgYSByZWxhdGl2ZWx5IHJvYnVzdCBRQyBwcm90b2NvbCksIGJ1dCB5b3UgY2FuIGNob29zZSBvdGhlciB2YWx1ZXMuCgpgYGB7cn0KIyBDYWxjdWxhdGUgdGhlIHBlcmNlbnRhZ2Ugb2YgdmFsaWQgU1dFIG9ic2VydmF0aW9ucyBwZXIgd2F0ZXIgeWVhcgpzaXRlX3N1bW1hcnlfYnlfd3llYXIgPC0gZGYgJT4lIAogIGdyb3VwX2J5KHNpdGVfaWQsIHd5ZWFyKSAlPiUgCiAgc3VtbWFyaXplKG5fZXhwZWN0ZWQgPSBpZmVsc2UoYW55KHd5ZWFyICUlIDQgPT0gMCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMzY2LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDM2NSksCiAgICAgICAgICAgIG5fb2JzID0gc3VtKCFpcy5uYShzd2VfbW0pKSwKICAgICAgICAgICAgcGN0X3ZhbGlkID0gKG5fb2JzIC8gbl9leHBlY3RlZCkgKiAxMDApICU+JSAKICB1bmdyb3VwKCkKCiMgUHJvdmlkZSBhIHRocmVzaG9sZCBvZiB2YWxpZCBvYnMgdGhhdCB3ZSdsbCBjb25zaWRlciB0byBiZSBhIGNvbXBsZXRlIHdhdGVyIHllYXIKcGN0X3ZhbGlkX3RocmVzaCA9IDEwMAoKIyBJZGVudGlmeSBzaXRlcyBhbmQgd2F0ZXIgeWVhcnMgdGhhdCBtZWV0IG91ciB0aHJlc2hvbGQKdmFsaWRfc2l0ZXNfd3llYXJzIDwtIHNpdGVfc3VtbWFyeV9ieV93eWVhciAlPiUgCiAgZmlsdGVyKHBjdF92YWxpZCA+PSBwY3RfdmFsaWRfdGhyZXNoKSAlPiUgCiAgc2VsZWN0KHNpdGVfaWQsIHd5ZWFyKQoKIyBGaWx0ZXIgdXNpbmcgaW5uZXIgam9pbiB0byBvbmx5IHNpdGVzIGFuZCB3YXRlciB5ZWFycyBpbiBvdXIgdmFsaWQgZGF0YSBmcmFtZQpkZl9maWx0ZXIgPC0gCiAgaW5uZXJfam9pbihkZiwgdmFsaWRfc2l0ZXNfd3llYXJzLAogICAgICAgICAgICAgYnkgPSBjKCJzaXRlX2lkIiwgInd5ZWFyIikpCmBgYAoKIyBDYWxjdWxhdGUgbWV0cmljcwoKTm93IHdlJ2xsIHVzZSBvdXIgU05PVEVMIGRhdGEgc3Vic2V0IHRvIGNvbXB1dGUgdmFyaW91cyBtZXRyaWNzOgoKLSBNYXhpbXVtIHNub3cgd2F0ZXIgZXF1aXZhbGVudCAoU1dFKQotIE1heGltdW0gU1dFIGRheSBvZiB3YXRlciB5ZWFyIChET1dZKQotIFNub3cgY292ZXIgZHVyYXRpb24KLSBTbm93LW9uIGRheQotIFNub3ctb2ZmIGRheQotIE1lbHQgc2Vhc29uIGxlbmd0aAotIFNub3dtZWx0IHJhdGUKLSBTbm93IHNlYXNvbmFsaXR5IG1ldHJpYyAoU1NNKQotIEFwcmlsIDEgU1dFCi0gU25vd21lbHQgYmVmb3JlIG1heCBTV0UKLSBTbm93bWVsdCBiZWZvcmUgbWF4IFNXRSBwZXJjZW50IG9mIHRvdGFsIHNub3dtZWx0Ci0gU25vd21lbHQgYmVmb3JlIG1heCBTV0UgdG8gbWF4IFNXRSByYXRpbwotIFNub3dtZWx0IGNlbnRlciBvZiBtYXNzIERPV1kKLSBQZWFrIFNXRSB0byBhbm51YWwgcHJlY2lwaXRhdGlvbiByYXRpbwotIEFuZCBzZXZlcmFsIG1ldGVvcm9sb2dpY2FsIGRhdGEgbWV0cmljcwoKYGBge3J9Cm1ldHJpY3MgPC0gZGZfZmlsdGVyICU+JSAKICBncm91cF9ieShzaXRlX2lkLCB3eWVhcikgJT4lIAogIHN1bW1hcml6ZShtYXhfc3dlX21tID0gbWF4U1dFKHN3ZV9tbSksIAogICAgICAgICAgICBtYXhfc3dlX2Rvd3kgPSBtYXhTV0VfRE9XWShzd2VfbW0sIGRvd3kpLCAKICAgICAgICAgICAgc2NkX2RheXMgPSBzY2Qoc3dlX21tKSwKICAgICAgICAgICAgc25vd19vbl9kYXkgPSBmaXJzdFNub3coc3dlX21tLCBkb3d5KSwKICAgICAgICAgICAgc25vd19vZmZfZGF5ID0gbGFzdFNub3coc3dlX21tLCBkb3d5LCBtYXhfc3dlX2Rvd3kpLAogICAgICAgICAgICBtZWx0X3NlYXNvbl9kYXlzID0gbWVsdFNlYXNvbihtYXhfc3dlX2Rvd3ksIHNub3dfb2ZmX2RheSksCiAgICAgICAgICAgIG1lbHRfcmF0ZV9tbV9kID0gbWVsdFJhdGUobWVsdF9zZWFzb25fZGF5cywgbWF4X3N3ZV9tbSksCiAgICAgICAgICAgIHNzbSA9IHNub3dTZWFzb25hbGl0eShzd2VfbW0pLCAKICAgICAgICAgICAgc3dlX2FwcjFfbW0gPSBhcHJpbDFTV0Uoc3dlX21tLCBkb3d5LCB3eWVhciksIAogICAgICAgICAgICBwcmVfbWF4X3N3ZV9tZWx0X21tID0gcHJlTWF4TWVsdChzd2VfbW0sIGRvd3ksIG1heF9zd2VfZG93eSksCiAgICAgICAgICAgIHByZV9tYXhfc3dlX21lbHRfdG90YWxfbWVsdF9wY3QgPSBwcmVNYXhNZWx0UGN0VG90YWxNZWx0KHByZV9tYXhfc3dlX21lbHRfbW0sIHN3ZV9tbSksCiAgICAgICAgICAgIHByZV9tYXhfc3dlX21lbHRfbWF4X3N3ZV9yYXRpbyA9IHByZU1heE1lbHRNYXhTV0VSYXRpbyhwcmVfbWF4X3N3ZV9tZWx0X21tLCBtYXhfc3dlX21tKSwKICAgICAgICAgICAgbWVsdF9jb21fZGF5ID0gbWVsdENvTShzd2VfbW0sIGRvd3kpLAogICAgICAgICAgICBzd2VfdG9fcHB0X3JhdGlvID0gc3dlVG9QcHRSYXRpbyhtYXhfc3dlX21tLCBwcHRfbW0pLAogICAgICAgICAgICBmYWxsX3BwdF9tbSA9IHNlYXNvbmFsUHJlY2lwKHBwdF9tbSwgZGF0ZSwgU0VBU09OID0gImZhbGwiKSwKICAgICAgICAgICAgd2ludGVyX3BwdF9tbSA9IHNlYXNvbmFsUHJlY2lwKHBwdF9tbSwgZGF0ZSwgU0VBU09OID0gIndpbnRlciIpLAogICAgICAgICAgICBzcHJpbmdfcHB0X21tID0gc2Vhc29uYWxQcmVjaXAocHB0X21tLCBkYXRlLCBTRUFTT04gPSAic3ByaW5nIiksCiAgICAgICAgICAgIHN1bW1lcl9wcHRfbW0gPSBzZWFzb25hbFByZWNpcChwcHRfbW0sIGRhdGUsIFNFQVNPTiA9ICJzdW1tZXIiKSwKICAgICAgICAgICAgYW5udWFsX3BwdF9tbSA9IHRvdGFsUHJlY2lwKHBwdF9tbSksCiAgICAgICAgICAgIGZhbGxfdGFpcl9kZWdDID0gc2Vhc29uYWxUZW1wKHRhaXJfYXZfZGVnQywgZGF0ZSwgU0VBU09OID0gImZhbGwiKSwKICAgICAgICAgICAgd2ludGVyX3RhaXJfZGVnQyA9IHNlYXNvbmFsVGVtcCh0YWlyX2F2X2RlZ0MsIGRhdGUsIFNFQVNPTiA9ICJ3aW50ZXIiKSwKICAgICAgICAgICAgc3ByaW5nX3RhaXJfZGVnQyA9IHNlYXNvbmFsVGVtcCh0YWlyX2F2X2RlZ0MsIGRhdGUsIFNFQVNPTiA9ICJzcHJpbmciKSwKICAgICAgICAgICAgc3VtbWVyX3RhaXJfZGVnQyA9IHNlYXNvbmFsVGVtcCh0YWlyX2F2X2RlZ0MsIGRhdGUsIFNFQVNPTiA9ICJzdW1tZXIiKSwKICAgICAgICAgICAgYW5udWFsX3RhaXJfZGVnQyA9IG1lYW5UZW1wKHRhaXJfYXZfZGVnQykpCmBgYAoKV2UgY2FuIHRha2UgYSBxdWljayBsb29rIGF0IHRoZXNlIHRhYnVsYXIgZGF0YS4KCmBgYHtyfQptZXRyaWNzICU+JSAKICBoZWFkKCkKYGBgCgpXZSBjYW4gYWxzbyBwbG90IHNvbWUgb2YgdGhlIG91dGNvbWVzLgoKIyMgTWF4aW11bSBTV0UKCmBgYHtyIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTcuNX0KZ2dwbG90KG1ldHJpY3MsIGFlcyh3eWVhciwgbWF4X3N3ZV9tbSkpICsgCiAgZ2VvbV9saW5lKCkgKyAKICBmYWNldF93cmFwKH5hcy5mYWN0b3Ioc2l0ZV9pZCksIG5jb2wgPSA0KSArCiAgbGFicyh4ID0gIldhdGVyIFllYXIiLCB5ID0gIk1heCBTV0UgKG1tKSIpCgpgYGAKCiMgU25vdyBjb3ZlciBkdXJhdGlvbiAoU0NEKQoKYGBge3IgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9Ny41fQpnZ3Bsb3QobWV0cmljcywgYWVzKHd5ZWFyLCBzY2RfZGF5cykpICsgCiAgZ2VvbV9saW5lKCkgKyAKICBmYWNldF93cmFwKH5hcy5mYWN0b3Ioc2l0ZV9pZCksIG5jb2wgPSA0KSArCiAgbGFicyh4ID0gIldhdGVyIFllYXIiLCB5ID0gIlNDRCAoZCkiKQoKYGBgCgojIyBTbm93IHNlYXNvbmFsaXR5IG1ldHJpYyAoU1NNKQoKYGBge3IgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9Ny41fQpnZ3Bsb3QobWV0cmljcywgYWVzKHd5ZWFyLCBzc20pKSArIAogIGdlb21fbGluZSgpICsgCiAgZmFjZXRfd3JhcCh+YXMuZmFjdG9yKHNpdGVfaWQpLCBuY29sID0gNCkgKwogIGxhYnMoeCA9ICJXYXRlciBZZWFyIiwgeSA9ICJTU00iKQoKYGBgCgojIyBQcmUtbWF4IFNXRSBzbm93bWVsdCBhcyBwZXJjZW50IG9mIHRvdGFsIHNub3dtZWx0CgpgYGB7ciBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD03LjV9CmdncGxvdChtZXRyaWNzLCBhZXMod3llYXIsIHByZV9tYXhfc3dlX21lbHRfdG90YWxfbWVsdF9wY3QpKSArIAogIGdlb21fbGluZSgpICsgCiAgZmFjZXRfd3JhcCh+YXMuZmFjdG9yKHNpdGVfaWQpLCBuY29sID0gNCkgKwogIGxhYnMoeCA9ICJXYXRlciBZZWFyIiwgeSA9ICJQcmUtTWF4IFNXRSBNZWx0ICglKSIpCgpgYGAKCkhvd2V2ZXIsIGl0IGlzIGhhcmQgdG8gdGVsbCB3aGF0LCBpZiBhbnl0aGluZywgaXMgaGFwcGVuaW5nIGF0IG91ciBzaXRlcyBvdmVyIHRpbWUuIFNvIHdoYXQgd2UnbGwgZG8gbmV4dCBpcyBjb21wdXRlIHNvbWUgdHJlbmRzLgoKIyBDb21wdXRlIHRyZW5kcwoKRmlyc3Qgd2UnbGwgbWFrZSBhIGZ1bmN0aW9uIHRvIG1ha2UgYSAibG9uZyIgdmVyc2lvbiBvZiBvdXIgYG1ldHJpY3NgIGRhdGFmcmFtZSBhbmQgdGhlbiBjb21wdXRlIHZhcmlvdXMgdHJlbmQgc3RhdHMsIHN1Y2ggYXMgdGhlIE1hbm4tS2VuZGFsbCBwLXZhbHVlIGFuZCBTZW4ncyBzbG9wZS4KCmBgYHtyfQphbmFseXplX3Nub3dfdHJlbmRzIDwtIGZ1bmN0aW9uKERGKSB7CiAgIyBQaXZvdCB0byBtYWtlIGxvbmcgZGF0YWZyYW1lCiAgZGZfbG9uZyA8LSBERiAlPiUKICAgIHBpdm90X2xvbmdlcigKICAgICAgY29scyA9IC1jKHNpdGVfaWQsIHd5ZWFyKSwgIyBjb3VsZCBhZGQgY29sdW1uIG5hbWVzIGFzIGFyZ3VtZW50IHRvIG1ha2UgZnVuY3Rpb24gZ2VuZXJhbGl6YWJsZQogICAgICBuYW1lc190byA9ICJtZXRyaWMiLAogICAgICB2YWx1ZXNfdG8gPSAidmFsdWUiCiAgICApICU+JQogICAgZmlsdGVyKCFpcy5uYSh2YWx1ZSkpICU+JSAgICAjIHJlbW92ZSBOQXMKICAgIGZpbHRlcighaXMuaW5maW5pdGUodmFsdWUpKSAgIyByZW1vdmUgSW5mcwogIAogICMgVGFrZSBkZl9sb25nIGFuZCBjb21wdXRlIHRyZW5kIHZhbHVlcyBwZXIgc2l0ZV9pZCBhbmQgbWV0cmljCiAgZGZfbG9uZyAlPiUKICAgIGdyb3VwX2J5KHNpdGVfaWQsIG1ldHJpYykgJT4lCiAgICBzdW1tYXJpc2UoCiAgICAgIG5feWVhcnMgPSBuKCksCiAgICAgIG1hbm5fa2VuZGFsbCA9IGxpc3QoTWFubktlbmRhbGwodmFsdWUpKSwKICAgICAgc2Vuc19zbG9wZSA9IGxpc3Qoc2Vucy5zbG9wZSh2YWx1ZSkpLAogICAgICAuZ3JvdXBzID0gImRyb3AiCiAgICApICU+JQogICAgbXV0YXRlKAogICAgICBta190YXUgPSBtYXBfZGJsKG1hbm5fa2VuZGFsbCwgfiAueCR0YXUpLAogICAgICBta19wID0gbWFwX2RibChtYW5uX2tlbmRhbGwsIH4gLngkc2wpLAogICAgICBzZW5fc2xvcGUgPSBtYXBfZGJsKHNlbnNfc2xvcGUsIH4gLngkZXN0aW1hdGVzKSwKICAgICAgc2VuX3AgPSBtYXBfZGJsKHNlbnNfc2xvcGUsIH4gLngkcC52YWx1ZSkKICAgICkgJT4lCiAgICBzZWxlY3Qoc2l0ZV9pZCwgbWV0cmljLCBuX3llYXJzLCBta190YXUsIG1rX3AsIHNlbl9zbG9wZSwgc2VuX3ApCn0KYGBgCgpOb3cgd2UnbGwgYXBwbHkgdGhpcyBmdW5jdGlvbiB0byBgbWV0cmljc2AuCgpgYGB7cn0KdHJlbmRzIDwtIGFuYWx5emVfc25vd190cmVuZHMobWV0cmljcykKdHJlbmRzICU+JSAKICBoZWFkKCkKYGBgCgpXZSBjYW4gdmlldyBkaXN0cmlidXRpb25zIG9mIHRoZSBNYW5uLUtlbmRhbGwgcC12YWx1ZXMgYW5kIFNlbidzIHNsb3Blcy4KCmBgYHtyIGZpZy5oZWlnaHQ9OS41LCBmaWcud2lkdGg9Ny41fQpnZ3Bsb3QodHJlbmRzLCBhZXMobWtfcCkpICsKICBnZW9tX2RlbnNpdHkoZmlsbCA9ICJsaWdodGJsdWUiKSArIAogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDAuMDUsIGx0eSA9ICJkYXNoZWQiKSArIAogIGZhY2V0X3dyYXAofm1ldHJpYywgbmNvbCA9IDMsIHNjYWxlcyA9ICJmcmVlIikgKwogIGxhYnMoeCA9ICJNYW5uLUtlbmRhbGwgcC12YWx1ZXMiLCB5ID0gIkRlbnNpdHkiKQpgYGAKCmBgYHtyIGZpZy5oZWlnaHQ9OS41LCBmaWcud2lkdGg9Ny41fQpnZ3Bsb3QodHJlbmRzLCBhZXMoc2VuX3Nsb3BlKSkgKwogIGdlb21fZGVuc2l0eShmaWxsID0gImxpZ2h0Ymx1ZSIpICsgCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgbHR5ID0gImRhc2hlZCIpICsgCiAgZmFjZXRfd3JhcCh+bWV0cmljLCBuY29sID0gMywgc2NhbGVzID0gImZyZWUiKSArCiAgbGFicyh4ID0gIlNlbidzIFNsb3BlcyIsIHkgPSAiRGVuc2l0eSIpCmBgYAoKV2UgY2FuIG5vdyByZS1leGFtaW5lIHNvbWUgb2YgdGhlIHByZXZpb3VzIHBsb3RzLCBsb29raW5nIGF0IG9ubHkgc2l0ZXMgd2l0aCBzaWduaWZpY2FudCBjaGFuZ2VzLgoKIyMgTWF4aW11bSBTV0Ugd2l0aCBzdGF0aXN0aWNhbGx5IHNpZ2luaWZpY2FudCB0cmVuZHMKCmBgYHtyfQpwX3RocmVzaCA9IDAuMDUKbWV0cmljcyAlPiUgCiAgZmlsdGVyKHNpdGVfaWQgJWluJSBmaWx0ZXIodHJlbmRzLCBtZXRyaWMgPT0gIm1heF9zd2VfbW0iICYgbWtfcCA8IDAuMDUpJHNpdGVfaWQpICU+JSAKICBnZ3Bsb3QoYWVzKHd5ZWFyLCBtYXhfc3dlX21tKSkgKyAKICBnZW9tX2xpbmUoKSArIAogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRiwgY29sb3IgPSAicmVkIikgKwogIGZhY2V0X3dyYXAofmFzLmZhY3RvcihzaXRlX2lkKSwgc2NhbGVzID0gImZyZWUiKSArCiAgbGFicyh4ID0gIldhdGVyIFllYXIiLCB5ID0gIk1heCBTV0UgKG1tKSIpCmBgYAoKIyMgU0NEIHdpdGggc3RhdGlzdGljYWxseSBzaWdpbmlmaWNhbnQgdHJlbmRzCgpgYGB7cn0KbWV0cmljcyAlPiUgCiAgZmlsdGVyKHNpdGVfaWQgJWluJSBmaWx0ZXIodHJlbmRzLCBtZXRyaWMgPT0gInNjZF9kYXlzIiAmIG1rX3AgPCAwLjA1KSRzaXRlX2lkKSAlPiUgCiAgZ2dwbG90KGFlcyh3eWVhciwgc2NkX2RheXMpKSArIAogIGdlb21fbGluZSgpICsgCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBGLCBjb2xvciA9ICJyZWQiKSArCiAgZmFjZXRfd3JhcCh+YXMuZmFjdG9yKHNpdGVfaWQpLCBzY2FsZXMgPSAiZnJlZSIpICsKICBsYWJzKHggPSAiV2F0ZXIgWWVhciIsIHkgPSAiU0NEIChkKSIpCmBgYAoKIyMgU25vdyBzZWFzb25hbGl0eSBtZXRyaWMgKFNTTSkgd2l0aCBzdGF0aXN0aWNhbGx5IHNpZ2luaWZpY2FudCB0cmVuZHMKCmBgYHtyfQptZXRyaWNzICU+JSAKICBmaWx0ZXIoc2l0ZV9pZCAlaW4lIGZpbHRlcih0cmVuZHMsIG1ldHJpYyA9PSAic3NtIiAmIG1rX3AgPCAwLjA1KSRzaXRlX2lkKSAlPiUgCiAgZ2dwbG90KGFlcyh3eWVhciwgc3NtKSkgKyAKICBnZW9tX2xpbmUoKSArIAogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRiwgY29sb3IgPSAicmVkIikgKwogIGZhY2V0X3dyYXAofmFzLmZhY3RvcihzaXRlX2lkKSwgc2NhbGVzID0gImZyZWUiKSArCiAgbGFicyh4ID0gIldhdGVyIFllYXIiLCB5ID0gIlNTTSIpCmBgYAoKIyMgUHJlLW1heCBTV0Ugc25vd21lbHQgYXMgcGVyY2VudCBvZiB0b3RhbCBzbm93bWVsdCB3aXRoIHN0YXRpc3RpY2FsbHkgc2lnaW5pZmljYW50IHRyZW5kcwoKYGBge3IsIGZpZy5oZWlnaHQ9NywgZmlnLndpZHRoPTcuNX0KbWV0cmljcyAlPiUgCiAgZmlsdGVyKHNpdGVfaWQgJWluJSBmaWx0ZXIodHJlbmRzLCBtZXRyaWMgPT0gInByZV9tYXhfc3dlX21lbHRfdG90YWxfbWVsdF9wY3QiICYgbWtfcCA8IDAuMDUpJHNpdGVfaWQpICU+JSAKICBnZ3Bsb3QoYWVzKHd5ZWFyLCBwcmVfbWF4X3N3ZV9tZWx0X3RvdGFsX21lbHRfcGN0KSkgKyAKICBnZW9tX2xpbmUoKSArIAogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRiwgY29sb3IgPSAicmVkIikgKwogIGZhY2V0X3dyYXAofmFzLmZhY3RvcihzaXRlX2lkKSwgc2NhbGVzID0gImZyZWUiLCBuY29sID0zKSArCiAgbGFicyh4ID0gIldhdGVyIFllYXIiLCB5ID0gIlByZS1NYXggU1dFIE1lbHQgKCUpIikKYGBgCgpXZSBjYW4gc3VtbWFyaXplIHRoZSB0cmVuZHMgZXZlbiBmdXJ0aGVyIHRvIHNlZSB3aGljaCBvbmVzIGhhdmUgdGhlIG1vc3QgcHJldmFsZW50IHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgcmVzdWx0cy4KCmBgYHtyfQpwX3RocmVzaCA9IDAuMDUKdHJlbmRfc3VtbWFyeSA8LSB0cmVuZHMgJT4lIAogIGdyb3VwX2J5KG1ldHJpYykgJT4lIAogIHN1bW1hcml6ZShta19wY3Rfc2lnID0gKHN1bShta19wIDwgcF90aHJlc2gpIC8gbigpKSAqIDEwMCwKICAgICAgICAgICAgc2VuX3Nsb3BlX2F2ID0gbWVhbihzZW5fc2xvcGUpLAogICAgICAgICAgICBzZW5fc2xvcGVfYXZfc2lnID0gbWVhbihzZW5fc2xvcGVbbWtfcCA8IHBfdGhyZXNoXSkpCnRyZW5kX3N1bW1hcnkgJT4lIAogIGFycmFuZ2UoLW1rX3BjdF9zaWcpCmBgYAoKTm93IHRoYXQgd2UndmUgbG9va2VkIGF0IHRoZXNlIGRhdGEsIHdlJ2xsIGV4cGxvcmUgdGhlIHVzZSBvZiBzcGF0aWFsIFNXRSBpbmZvcm1hdGlvbiBpbiBvdXIgd29yay4=